package com.zgis.module.data.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.baomidou.mybatisplus.annotation.DbType;
import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
import com.zgis.module.bas.bean.CxPro;
import com.zgis.module.bas.bean.CxRet;
import com.zgis.module.bas.bean.CxSqlExp;
import com.zgis.module.bas.bean.CxSqlPro;
import com.zgis.module.bas.constant.CxConsts;
import com.zgis.module.bas.constant.CxErrors;
import com.zgis.module.bas.constant.CxKeys;
import com.zgis.module.bas.constant.CxSymbol;
import com.zgis.module.bas.bean.CxEntity;
import com.zgis.module.bas.bean.CxFld;
import cn.iocoder.yudao.framework.mybatis.core.mapper.CommonMapperX;
import com.zgis.module.bas.util.CxDate;
import com.zgis.module.bas.util.CxJson;
import com.zgis.module.bas.util.CxRegex;
import com.zgis.module.bas.util.CxValueUtil;
import com.zgis.module.data.manager.CxSvrBatch;
import com.zgis.module.db.interfaces.ICxEntity;
import com.zgis.module.db.interfaces.ICxFld;
import com.zgis.module.db.interfaces.ICxSqlExp;
import com.zgis.module.db.service.TableCacheService;
import com.zgis.module.db.util.CxParam;
import com.zgis.module.db.util.CxUnionTables;
import com.zgis.module.db.util.SqlUtil;
import org.springframework.beans.factory.annotation.Value;
import java.util.Map;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;

/**
 * 修改记录:
 * 24-09-29     zhengchaojun             首次创建
 */

/**
 * 类描述：本地服务
 */
@Service
public class SvrDataServiceImpl
{
    @Resource
    private CommonMapperX mapBase;

    @Resource
    private PlatformTransactionManager dstm;

    @Resource(name = "redisCxEntityImpl")
    private ICxEntity cxEntity;

    @Resource(name = "redisCxFldImpl")
    private ICxFld cxFld;

    @Resource(name = "redisCxSqlExpImpl")
    private ICxSqlExp cxSqlExp;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private com.zgis.module.db.service.TableCacheService tableCacheService;

    /**
     * 根据ID查询
     *
     * @param params 参数
     * @return 查询结果
     * @throws Exception 异常
     */
    public ObjectNode queryId(JsonNode params) throws Exception
    {
        CxEntity            entity = getEntity(params);
        ObjectNode          jo     = CxJson.objectNode();
        String              cols   = CxParam.getStr(params, CxKeys.cols);
        String              id     = CxParam.getStr(params, CxKeys.id);
        Map<String, Object> data   = mapBase.queryById(entity.getName(), CxValueUtil.isEmpty(cols) ? CxSymbol.start : cols, Integer.parseInt(id));
        if (data == null)
        {
            throw new Exception(CxErrors.noData);
        }
        else
        {
            CxRet.setSuccess(jo, data, true);
            return jo;
        }
    }

    /**
     * 根据ID0查询
     *
     * @param params 参数
     * @return 查询结果
     * @throws Exception 异常
     */
    public ObjectNode queryId0(JsonNode params) throws Exception
    {
        CxEntity                  entity  = getEntity(params);
        String                    cols    = CxParam.getStr(params, CxKeys.cols);
        String                    id      = CxParam.getStr(params, CxKeys.id);
        String                    orderby = CxParam.getStr(params, CxKeys.orderby);
        ObjectNode                jo      = CxJson.objectNode();
        List<Map<String, Object>> rs      = mapBase.queryById0(entity.getName(), CxValueUtil.isEmpty(cols) ? CxSymbol.start : cols, Integer.parseInt(id), orderby);
        CxRet.setSuccess(jo, rs, false);
        return jo;
    }

    /**
     * 查询所有数据
     *
     * @param table   表名
     * @param cols    返回列，*或者多个列名
     * @param orderby 排序
     * @return 查询结果
     * @throws Exception 异常
     */
    public Object queryAll(String table, String cols, String orderby) throws Exception
    {
        return mapBase.queryAll(table, CxValueUtil.isEmpty(cols) ? "*" : cols, orderby);
    }

    /**
     * 查询所有数据
     *
     * @param params 参数
     * @return 查询结果
     * @throws Exception 异常
     */
    public ObjectNode queryAll(JsonNode params) throws Exception
    {
        CxEntity            entity  = getEntity(params);
        List<CxFld>         flds    = getFldList(entity.getName());
        String              table   = entity.getName();
        String              cols    = CxParam.getStr(params, CxKeys.cols);
        String              excols  = CxParam.getStr(params, CxKeys.excols);
        String              orderby = CxParam.getStr(params, CxKeys.orderby);
        ObjectNode          jo      = CxJson.objectNode();
        List<Map<String, Object>> rs;

        // 处理返回列
        if (CxValueUtil.isEmpty(cols))
        {
            if (!CxValueUtil.isEmpty(excols))
            {
                // 如果指定了排除字段，则返回除排除字段外的所有字段
                cols = SqlUtil.getColsSelectEx(flds, excols).substring(3); // 去掉 "id,"
            }
            else
            {
                // 默认返回所有字段
                cols = "*";
            }
        }

        rs = mapBase.queryAll(table, cols, CxValueUtil.isEmpty(orderby) ? "" : orderby);
        CxRet.setSuccess(jo, rs, false);
        return jo;
    }

    /**
     * 查询(单表查询 / 联合查询 / 内置SQL / 条件 / 分页 / 部分列)
     *
     * @param params 参数
     * @param qryNum 数据操作权限
     * @return 查询结果
     * @throws Exception 异常
     */
    public ObjectNode query(JsonNode params, char qryNum) throws Exception
    {
        CxEntity     entity = getEntity(params);
        List<CxFld>  flds   = getFldList(entity.getName());
        CxSqlExp     sqlExp;
        String   tables = CxParam.getStr(params, CxKeys.tables);         //多表
        String   cols   = null;
        String   excols = CxParam.getStr(params, CxKeys.excols);         //服务调用传的 excols
        String   exp    = null;
        String   sep    = null;

        String   strTypesC = null;                                        //配置 参数类型-字符串
        String   strValsC  = null;                                        //配置 参数值-字符串
        String[] arrTypeC  = null;                                        //配置 参数类型-数组
        String[] arrValC   = null;                                        //配置 参数值-数组
        int      paraNumC  = 0;                                           //配置 参数-个数

        String[] arrValP = null;                                         //传参 参数值-数组
        int      paraNum;                                                //当前参数个数

        CxUnionTables unionTabls = new CxUnionTables(entity, cxEntity, cxFld);   //联合查询-表对象
        String        sqlTables;                                                //联合查询-表名
        String        sqlCols;                                                  //联合查询-列名

        String key      = CxParam.getStr(params, CxKeys._key);
        String strColsP = CxParam.getStr(params, CxKeys.cols);         //服务调用传的 cols
        String strValsP = CxParam.getStr(params, CxKeys.vals);         //服务调用传的 vals
        String strArgs  = CxParam.getStr(params, CxKeys.args);          //服务调用传的 SQL参数
        String orderby  = CxParam.getStr(params, CxKeys.orderby);
        String strInit  = CxParam.getStr(params, CxKeys.init);

        String dbId       = getDbId();                                           //获取数据库类型
        String sqlSpatial = CxParam.getSpatialQuery(dbId, 0, params);        //是否有空间查询
        int    geomerty   = CxParam.getInt(params, CxKeys.geometry, 0);    //是否取空间字段

        Map<String, Object> values  = new HashMap<>();
        ObjectNode          jo      = CxJson.objectNode();
        List                rs      = null;
        boolean             bGeoCol = false;

        String  str;
        int     i, j, num;
        Boolean bObj = false;

        if (geomerty == 1)
        {
            bGeoCol = true;
        }

        //取配置对象
        if (key != null && !key.isEmpty())
        {
            // 2、取usersql值
            sqlExp = cxSqlExp.queryCxSqlExp(key);
            if (CxValueUtil.isEmpty(sqlExp))
            {
                throw new Exception("在cx_sqlexp中未找key字段");
            }
            tables = sqlExp.getTables();
            exp = sqlExp.getExp();
            cols = sqlExp.getCols();
            strTypesC = sqlExp.getTypes();
            strValsC = sqlExp.getVals();
            sep = sqlExp.getSep();

            //检查表名是否错误
            if (sqlExp.getTab() != null && !sqlExp.getTab().isEmpty() && sqlExp.getTab().compareToIgnoreCase(entity.getName()) != 0)
                throw new Exception(CxErrors.errTable + CxConsts.MINUS + sqlExp.getTab());
        }

        //如果没有联合查询，则默认取全部字段
        if (tables == null)
        {
            tables = "";
        }

        //表达式、分隔符
        if (exp == null)
        {
            exp = "";
        }

        if (CxValueUtil.isEmpty(sep))
        {
            sep = CxConsts.defSep;
        }

        //返回列
        if (strColsP != null && !strColsP.isEmpty()) cols = strColsP;
        if (cols == null || cols.isEmpty())
        {
            if (excols != null && !excols.isEmpty())
            {
                cols = SqlUtil.getColsSelectEx(flds, excols).substring(3);    // id,***,**, 需要去掉id,
            }
            else
            {
                cols = "";
            }
        }

        //传参 参数值
        if (strValsP != null && !strValsP.isEmpty())
        {
            arrValP = strValsP.split(sep);
            paraNum = arrValP.length;
            //检查值
            for (i = 0; i < paraNum; i++)
                if (arrValP[i].compareToIgnoreCase(CxConsts.$null) == 0) arrValP[i] = null;
        }

        //配置 参数类型和值
        if (strTypesC != null && !strTypesC.isEmpty())
        {
            char ch;

            arrTypeC = strTypesC.split(CxConsts.defSep);
            paraNumC = arrTypeC.length;
            arrValC = strValsC.split(sep);

            if (paraNumC != arrValC.length)
                throw new Exception(CxErrors.errValType + CxConsts.MINUS + strTypesC + " " + strValsC);

            //用传来的值去替换配置中值，配置里面写法如：{1}，{2} 分别对应传递进来的值, {var} 这样的格式替换传进来的变量
            for (i = 0; i < paraNumC; i++)
            {
                str = arrValC[i];
                num = str.length();
                if (num >= 3 && str.charAt(0) == '{' && str.charAt(num - 1) == '}')
                {
                    str = str.substring(1, num - 1);
                    ch = str.charAt(0);
                    if (ch >= '0' && ch <= '9')
                    {
                        j = Integer.parseInt(str) - 1;
                        if (j >= arrValP.length || j < 0) throw new Exception(CxErrors.errParam + CxConsts.MINUS + str);
                        arrValC[i] = arrValP[j];
                    }
                    else arrValC[i] = CxParam.getStr(params, str);
                }
            }
        }

        //如果存在拼SQL的情况，需要额外注意，配置里面的写法如下： {abc},abc-是参数的名称
        if (exp != null && !exp.isEmpty())
        {
            Matcher match = CxRegex.refVarPtn.matcher(exp);

            while (match.find())
            {
                str = match.group();
                num = str.length();
                str = str.substring(1, num - 1);

                //检查传入的参数是否有SQL注入并替换, 在参数两边增加 '',强制转换成字符串
                exp = exp.replaceAll("\\{" + str + "\\}", SqlUtil.paramIn(CxParam.getStr(params, str)));
            }
        }

        //这里得到了所有的参数，整理成 数据库 参数
        for (i = 0; i < paraNumC; i++)
        {
            exp = replaceChar(exp, "#{values." + i + "}");
            CxParam.paramsObj(values, "" + i, arrTypeC[i], arrValC[i]);
        }
        paraNum = paraNumC;

        //表，列
        unionTabls.parseTables(tables);                    //确定表
        unionTabls.parseCols(cols);                        //确定列
        sqlTables = unionTabls.getTabnames();            //SQL表
        sqlCols = unionTabls.getColnames(dbId, bGeoCol);//SQL列


        //根据外面的参数来拼SQL,格式: [key, oprr, [value], [连接符]]
        if (strArgs != null && !strArgs.isEmpty())
        {
            String   sqlt1   = "", sqlt2;
            String[] arrArgs = strArgs.split(CxConsts.SIMICOLON);
            String[] arrArg;
            int      typeS   = 0;
            int      oper, conc;

            for (i = 0; i < arrArgs.length; i++)
            {
                arrArg = arrArgs[i].split(CxConsts.conSep);
                sqlt2 = "";

                //检查是否是数据库中的列
                typeS = unionTabls.getColType(arrArg[0]);

                //操作符
                oper = Integer.parseInt(arrArg[1]);
                switch (oper)
                {
                    case 1:
                        arrArg[1] = "=";
                        break;
                    case 2:
                        arrArg[1] = "!=";
                        break;
                    case 3:
                        arrArg[1] = ">";
                        break;
                    case 4:
                        arrArg[1] = ">=";
                        break;
                    case 5:
                        arrArg[1] = "<";
                        break;
                    case 6:
                        arrArg[1] = "<=";
                        break;
                    case 7:
                        arrArg[1] = " LIKE ";
                        break;
                    case 8:
                        arrArg[1] = " IS NULL";
                        break;
                    case 9:
                        arrArg[1] = " IS NOT NULL";
                        break;
                    case 10:
                        arrArg[1] = " IN";
                        break;
                    default:
                        throw new Exception(CxErrors.errParam + CxConsts.MINUS + oper);
                }

                //连接符
                conc = Integer.parseInt(arrArg[3]);
                switch (conc)
                {
                    case 1:
                        arrArg[3] = " AND ";
                        break;
                    case 2:
                        arrArg[3] = " OR ";
                        break;
                    default:
                        arrArg[3] = "";
                        break;
                }

                //拼 列 + 操作符 + 值
                switch (oper)
                {
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                        sqlt2 = arrArg[0] + arrArg[1] + "#{values." + paraNum + "}";                //col = #{values.n
                        CxParam.paramsObj(values, "" + paraNum, typeS, arrArg[2].compareToIgnoreCase(CxConsts.$null) == 0 ? null : arrArg[2]);
                        paraNum++;
                        break;
                    case 8:
                    case 9:
                        sqlt2 = arrArg[0] + arrArg[1];                                                //col IS NULL
                        break;
                    case 10:
                        sqlt2 = arrArg[0] + arrArg[1] + "(" + SqlUtil.paramIn(arrArg[2]) + ")";        //col IN('***','***')
                        break;
                }

                //拼连接符
                if (i == 0 && exp.isEmpty()) sqlt1 += sqlt2;
                else
                {
                    if (conc != 1 && conc != 2) throw new Exception(CxErrors.errParam + CxConsts.MINUS + conc);
                    sqlt1 += arrArg[3] + sqlt2;
                }
            }

            exp += sqlt1;
        }

        //拼接空间查询
        if (sqlSpatial != null)
        {
            if (exp != null && !exp.isEmpty()) exp += " AND " + sqlSpatial;
            else exp += sqlSpatial;
        }

        //检查order by 的SQL注入
        if (orderby != null && !orderby.isEmpty())
        {
            if (!SqlUtil.filterOrderby(orderby, unionTabls))
                throw new Exception(CxErrors.errParam + CxConsts.MINUS + orderby);
        }

        String sql    = "";
        String expVal = "";
        if (exp.isEmpty())
        {
            expVal = sqlTables + (orderby != null && orderby.length() > 0 ? " ORDER BY " + orderby : "");
            sql = "SELECT " + sqlCols + " FROM " + expVal;
        }
        else
        {
            expVal = sqlTables + " WHERE " + exp + (orderby != null && orderby.length() > 0 ? " ORDER BY " + orderby : "");
            sql = "SELECT " + sqlCols + " FROM " + expVal;
        }

        //执行SQL
        if (strInit != null)
        {
            int init     = Integer.parseInt(strInit);
            int pageNo   = Integer.parseInt(CxParam.getStr(params, CxKeys.pageno));
            int pageSize = Integer.parseInt(CxParam.getStr(params, CxKeys.pagesize));

            // 先查询总数
            String countSql = "SELECT COUNT(*) as count FROM " + expVal;
            List<Map<String, Object>> countResult = mapBase.query(countSql, values);
            num = ((Number) countResult.get(0).get("count")).intValue();

            // 添加分页限制
            int offset = (pageNo - 1) * pageSize;
            String pageSql = sql + " LIMIT " + pageSize + " OFFSET " + offset;
            rs = mapBase.query(pageSql, values);

            if (init == 0)
            {
                jo.put(CxKeys.total, num);
                bObj = true;
            }
        }
        else
        {
            rs = mapBase.query(sql, values);
        }

        CxRet.setSuccess(jo, rs, bObj);
        return jo;
    }

    /**
     * 查询SQL
     *
     * @param params 参数
     * @param qryNum 数据操作权限
     * @return 查询结果
     * @throws Exception 异常
     */
    public Object querySql(JsonNode params, char qryNum) throws Exception
    {
        CxEntity    entity = getEntity(params);
        CxSqlExp    sqlExp;
        String      tables = CxParam.getStr(params, CxKeys.tables);         //多表
        String   cols   = CxParam.getStr(params, CxKeys.cols);                //服务调用传的 cols
        String   exp    = null;
        String   sep    = null;

        String   strTypesC = null;                                        //配置 参数类型-字符串
        String   strValsC  = null;                                        //配置 参数值-字符串
        String[] arrTypeC  = null;                                        //配置 参数类型-数组
        String[] arrValC   = null;                                            //配置 参数值-数组
        int      paraNumC  = 0;                                            //配置 参数-个数

        String[] arrValP = null;                                            //传参 参数值-数组
        int      paraNum;                                                //当前参数个数

        String key         = CxParam.getStr(params, CxKeys._key);
        String strValsP    = CxParam.getStr(params, CxKeys.vals);         //服务调用传的 vals
        String strArgs     = CxParam.getStr(params, CxKeys.args);          //服务调用传的 SQL参数
        String strArgsType = CxParam.getStr(params, CxKeys.argstype);  //服务调用传的 SQL参数的类型

        String orderby = CxParam.getStr(params, CxKeys.orderby);
        String groupby = CxParam.getStr(params, CxKeys.groupby);
        String strInit = CxParam.getStr(params, CxKeys.init);

        String dbId       = getDbId();                                 //获取数据库类型
        String sqlSpatial = CxParam.getSpatialQuery(dbId, 0, params); //是否有空间查询
        String sqlstr     = "";

        Map<String, Object>       values = new HashMap<>();
        ObjectNode                jo     = CxJson.objectNode();
        List<Map<String, Object>> rs;

        String  str;
        int     i, j, num;
        Boolean bObj = false;

        //取配置对象
        if (key != null && !key.isEmpty())
        {
            sqlExp = cxSqlExp.queryCxSqlExp(key);
            if (sqlExp == null) throw new Exception(CxErrors.errKey + CxConsts.MINUS + key);
            sqlstr = sqlExp.getExp();
            strTypesC = sqlExp.getTypes();
            strValsC = sqlExp.getVals();
            sep = sqlExp.getSep();

            //检查表名是否错误
            if (sqlExp.getTab() != null && !sqlExp.getTab().isEmpty() && sqlExp.getTab().compareToIgnoreCase(entity.getName()) != 0)
                throw new Exception(CxErrors.errTable + CxConsts.MINUS + sqlExp.getTab());
        }

        //表达式、分隔符
        if (exp == null) exp = "";
        if (sep == null || sep.isEmpty()) sep = CxConsts.defSep;

        //传参 参数值
        if (strValsP != null && !strValsP.isEmpty())
        {
            arrValP = strValsP.split(sep);
            paraNum = arrValP.length;
            //检查值
            for (i = 0; i < paraNum; i++)
                if (arrValP[i].compareToIgnoreCase(CxConsts.$null) == 0) arrValP[i] = null;
        }

        //配置 参数类型和值
        if (strTypesC != null && !strTypesC.isEmpty())
        {
            char ch;

            arrTypeC = strTypesC.split(CxConsts.defSep);
            paraNumC = arrTypeC.length;
            arrValC = strValsC.split(sep);

            if (paraNumC != arrValC.length)
                throw new Exception(CxErrors.errValType + CxConsts.MINUS + strTypesC + " " + strValsC);

            //用传来的值去替换配置中值，配置里面写法如：{1}，{2} 分别对应传递进来的值, {var} 这样的格式替换传进来的变量
            for (i = 0; i < paraNumC; i++)
            {
                str = arrValC[i];
                num = str.length();
                if (num >= 3 && str.charAt(0) == '{' && str.charAt(num - 1) == '}')
                {
                    str = str.substring(1, num - 1);
                    ch = str.charAt(0);
                    if (ch >= '0' && ch <= '9')
                    {
                        j = Integer.parseInt(str) - 1;
                        if (j >= arrValP.length || j < 0) throw new Exception(CxErrors.errParam + CxConsts.MINUS + str);
                        arrValC[i] = arrValP[j];
                    }
                    else arrValC[i] = CxParam.getStr(params, str);
                }
            }
        }

        //如果存在拼SQL的情况，需要额外注意，配置里面的写法如下： {abc},abc-是参数的名称
        if (exp != null && !exp.isEmpty())
        {
            Matcher match = CxRegex.refVarPtn.matcher(exp);

            while (match.find())
            {
                str = match.group();
                num = str.length();
                str = str.substring(1, num - 1);

                //检查传入的参数是否有SQL注入并替换, 在参数两边增加 '',强制转换成字符串
                exp = exp.replaceAll("\\{" + str + "\\}", SqlUtil.paramIn(CxParam.getStr(params, str)));
            }
        }

        //这里得到了所有的参数，整理成 MyBatis 参数
        for (i = 0; i < paraNumC; i++)
        {
            sqlstr = replaceChar(sqlstr, "#{values." + i + "}");
            CxParam.paramsObj(values, "" + i, arrTypeC[i], arrValC[i]);
        }
        paraNum = paraNumC;

        //根据外面的参数来拼SQL,格式: [key, oprr, [value], [连接符]]
        if (strArgs != null && !strArgs.isEmpty())
        {
            String   sqlt1       = "", sqlt2;
            String[] arrArgs     = strArgs.split(CxConsts.SIMICOLON);
            String[] arrArg;
            String[] arrArgsType = strArgsType.split(CxConsts.defSep);
            int      typeS;
            int      oper, conc;

            for (i = 0; i < arrArgs.length; i++)
            {
                arrArg = arrArgs[i].split(CxConsts.conSep);
                sqlt2 = "";

                //转换参数的类型
                typeS = CxParam.getObjType(arrArgsType[i]);

                //操作符
                oper = Integer.parseInt(arrArg[1]);
                switch (oper)
                {
                    case 1:
                        arrArg[1] = "=";
                        break;
                    case 2:
                        arrArg[1] = "!=";
                        break;
                    case 3:
                        arrArg[1] = ">";
                        break;
                    case 4:
                        arrArg[1] = ">=";
                        break;
                    case 5:
                        arrArg[1] = "<";
                        break;
                    case 6:
                        arrArg[1] = "<=";
                        break;
                    case 7:
                        arrArg[1] = " LIKE ";
                        break;
                    case 8:
                        arrArg[1] = " IS NULL";
                        break;
                    case 9:
                        arrArg[1] = " IS NOT NULL";
                        break;
                    case 10:
                        arrArg[1] = " IN";
                        break;
                    default:
                        throw new Exception(CxErrors.errParam + CxConsts.MINUS + oper);
                }

                //连接符
                conc = Integer.parseInt(arrArg[3]);
                switch (conc)
                {
                    case 1:
                        arrArg[3] = " AND ";
                        break;
                    case 2:
                        arrArg[3] = " OR ";
                        break;
                    default:
                        arrArg[3] = "";
                        break;
                }

                //拼 列 + 操作符 + 值
                switch (oper)
                {
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                        sqlt2 = arrArg[0] + arrArg[1] + "#{values." + paraNum + "}";                //col = #{values.n
                        CxParam.paramsObj(values, "" + paraNum, typeS, arrArg[2].compareToIgnoreCase(CxConsts.$null) == 0 ? null : arrArg[2]);
                        paraNum++;
                        break;
                    case 8:
                    case 9:
                        sqlt2 = arrArg[0] + arrArg[1];                                                //col IS NULL
                        break;
                    case 10:
                        sqlt2 = arrArg[0] + arrArg[1] + "(" + SqlUtil.paramIn(arrArg[2]) + ")";        //col IN('***','***')
                        break;
                }

                //第一个条件默认不加连接符
                if (i == 0 && exp.isEmpty()) sqlt1 += sqlt2;
                else
                {
                    if (conc != 1 && conc != 2) throw new Exception(CxErrors.errParam + CxConsts.MINUS + conc);
                    sqlt1 += arrArg[3] + sqlt2;
                }
            }

            exp += sqlt1;
        }

        //拼接空间查询
        if (sqlSpatial != null)
        {
            if (exp != null && !exp.isEmpty()) exp += " AND " + sqlSpatial;
            else exp += sqlSpatial;
        }

        //执行SQL
        String sql = sqlstr;
        sql = SqlUtil.replace(tables, sql, "@tab");          //替换表名
        sql = SqlUtil.replace(cols, sql, "@col");            //替换列名
        sql = SqlUtil.replace(exp, sql, "@exp");            //替换查询条件
        sql = sql + (orderby != null && orderby.length() > 0 ? " ORDER BY " + orderby : "") + (groupby != null && groupby.length() > 0 ? " GROUP BY " + groupby : "");

        if (strInit != null)
        {
            int init     = Integer.parseInt(strInit);
            int pageNo   = Integer.parseInt(CxParam.getStr(params, CxKeys.pageno));
            int pageSize = Integer.parseInt(CxParam.getStr(params, CxKeys.pagesize));

            // 先查询总数
            String countSql = "SELECT COUNT(*) as count FROM (" + sql + ") as temp_count";
            List<Map<String, Object>> countResult = mapBase.query(countSql, values);
            num = ((Number) countResult.get(0).get("count")).intValue();

            // 添加分页限制
            int offset = (pageNo - 1) * pageSize;
            String pageSql = sql + " LIMIT " + pageSize + " OFFSET " + offset;
            rs = mapBase.query(pageSql, values);

            if (init == 0)
            {
                jo.put(CxKeys.total, num);
                bObj = true;
            }
        }
        else
        {
            rs = mapBase.query(sql, values);
        }

        CxRet.setSuccess(jo, rs, bObj);
        return jo;
    }

    /**
     * 查询序列
     *
     * @param params 参数
     * @return 查询结果
     * @throws Exception 异常
     */
    public ObjectNode querySeq(JsonNode params) throws Exception
    {
        CxEntity   entity = getEntity(params);
        ObjectNode jo     = CxJson.objectNode();
        Object        rs  =  mapBase.querySeq(entity.getName()).get("id");

        CxRet.setSuccess(jo, CxKeys.id, rs);
        return jo;
    }

    /**
     * 统计(单表 / 多表 / 内置SQL / 条件 / 统计函数)
     *
     * @param params 参数
     * @return 统计结果
     * @throws Exception 异常
     */
    public ObjectNode stat(JsonNode params) throws Exception
    {
        CxEntity    entity = getEntity(params);
        List<CxFld> flds   = getFldList(entity.getName());
        CxSqlExp    sqlExp;
        String   tables = CxParam.getStr(params, CxKeys.tables);         //多表
        String   cols   = null;
        String   exp    = null;
        String   sep    = null;

        String   strTypesC = null;                                        //配置 参数类型-字符串
        String   strValsC  = null;                                        //配置 参数值-字符串
        String[] arrTypeC  = null;                                        //配置 参数类型-数组
        String[] arrValC   = null;                                            //配置 参数值-数组
        int      paraNumC  = 0;                                            //配置 参数-个数

        String[] arrValP = null;                                            //传参 参数值-数组
        int      paraNum = 0;                                            //当前参数个数

        CxUnionTables unionTabls = new CxUnionTables(entity, cxEntity, cxFld);   //联合查询-表对象
        String        sqlTables;                                                //联合查询-表名
        String        sqlCols;                                                  //联合查询-列名

        String key      = CxParam.getStr(params, CxKeys._key);
        String strColsP = CxParam.getStr(params, CxKeys.cols);         //服务调用传的 cols
        String strValsP = CxParam.getStr(params, CxKeys.vals);         //服务调用传的 vals
        String strArgs  = CxParam.getStr(params, CxKeys.args);          //服务调用传的 SQL参数

        Map<String, Object>       values = new HashMap<>();
        ObjectNode                jo     = CxJson.objectNode();
        List<Map<String, Object>> rs;

        String str;
        int    i, j, num;

        //取配置对象
        if (key != null && !key.isEmpty())
        {
            sqlExp = cxSqlExp.queryCxSqlExp(key);
            if (sqlExp == null) throw new Exception(CxErrors.errKey + CxConsts.MINUS + key);
            tables = sqlExp.getTables();
            exp = sqlExp.getExp();
            cols = sqlExp.getCols();
            strTypesC = sqlExp.getTypes();
            strValsC = sqlExp.getVals();
            sep = sqlExp.getSep();

            //检查表名是否错误
            if (sqlExp.getTab() != null && !sqlExp.getTab().isEmpty() && sqlExp.getTab().compareToIgnoreCase(entity.getName()) != 0)
                throw new Exception(CxErrors.errTable + CxConsts.MINUS + sqlExp.getTab());
        }

        //如果没有联合查询，则默认取全部字段
        if (tables == null) tables = "";
        if (cols == null) cols = "";

        //表达式、分隔符
        if (exp == null) exp = "";
        if (sep == null || sep.isEmpty()) sep = CxConsts.defSep;

        //返回列
        if (strColsP != null && !strColsP.isEmpty()) cols = strColsP;

        //传参 参数值
        if (strValsP != null && !strValsP.isEmpty())
        {
            arrValP = strValsP.split(sep);
            paraNum = arrValP.length;
            //检查值
            for (i = 0; i < paraNum; i++)
                if (arrValP[i].compareToIgnoreCase(CxConsts.$null) == 0) arrValP[i] = null;
        }

        //配置 参数类型和值
        if (strTypesC != null && !strTypesC.isEmpty())
        {
            char ch;

            arrTypeC = strTypesC.split(CxConsts.defSep);
            paraNumC = arrTypeC.length;
            arrValC = strValsC.split(sep);

            if (paraNumC != arrValC.length)
                throw new Exception(CxErrors.errValType + CxConsts.MINUS + strTypesC + " " + strValsC);

            //用传来的值去替换配置中值，配置里面写法如：{1}，{2} 分别对应传递进来的值, {var} 这样的格式替换传进来的变量
            for (i = 0; i < paraNumC; i++)
            {
                str = arrValC[i];
                num = str.length();
                if (num >= 3 && str.charAt(0) == '{' && str.charAt(num - 1) == '}')
                {
                    str = str.substring(1, num - 1);
                    ch = str.charAt(0);
                    if (ch >= '0' && ch <= '9')
                    {
                        j = Integer.parseInt(str) - 1;
                        if (j >= arrValP.length || j < 0) throw new Exception(CxErrors.errParam + CxConsts.MINUS + str);
                        arrValC[i] = arrValP[j];
                    }
                    else arrValC[i] = CxParam.getStr(params, str);
                }
            }
        }

        //如果存在拼SQL的情况，需要额外注意，配置里面的写法如下： {abc},abc-是参数的名称
        if (exp != null && !exp.isEmpty())
        {
            Matcher match = CxRegex.refVarPtn.matcher(exp);

            while (match.find())
            {
                str = match.group();
                num = str.length();
                str = str.substring(1, num - 1);

                //检查传入的参数是否有SQL注入并替换, 在参数两边增加 '',强制转换成字符串
                exp = exp.replaceAll("\\{" + str + "\\}", SqlUtil.paramIn(CxParam.getStr(params, str)));
            }
        }

        //这里得到了所有的参数，整理成 MyBatis 参数
        for (i = 0; i < paraNumC; i++)
        {
            exp = replaceChar(exp, "#{values." + i + "}");
            CxParam.paramsObj(values, "" + i, arrTypeC[i], arrValC[i]);
        }
        paraNum = paraNumC;

        //表，列
        unionTabls.parseTables(tables);                    //确定表
        sqlTables = unionTabls.getTabnames();            //SQL表
        sqlCols = unionTabls.statCols(cols, sep);        //SQL列

        //根据外面的参数来拼SQL,格式: [key, oprr, [value], [连接符]]
        if (strArgs != null && !strArgs.isEmpty())
        {
            String   sqlt1   = "", sqlt2;
            String[] arrArgs = strArgs.split(CxConsts.SIMICOLON);
            String[] arrArg;
            int      typeS;
            int      oper, conc;

            for (i = 0; i < arrArgs.length; i++)
            {
                arrArg = arrArgs[i].split(CxConsts.conSep);
                sqlt2 = "";

                //检查是否是数据库中的列
                typeS = unionTabls.getColType(arrArg[0]);

                //操作符
                oper = Integer.parseInt(arrArg[1]);
                switch (oper)
                {
                    case 1:
                        arrArg[1] = "=";
                        break;
                    case 2:
                        arrArg[1] = "!=";
                        break;
                    case 3:
                        arrArg[1] = ">";
                        break;
                    case 4:
                        arrArg[1] = ">=";
                        break;
                    case 5:
                        arrArg[1] = "<";
                        break;
                    case 6:
                        arrArg[1] = "<=";
                        break;
                    case 7:
                        arrArg[1] = " LIKE ";
                        break;
                    case 8:
                        arrArg[1] = " IS NULL";
                        break;
                    case 9:
                        arrArg[1] = " IS NOT NULL";
                        break;
                    case 10:
                        arrArg[1] = " IN";
                        break;
                    default:
                        throw new Exception(CxErrors.errParam + CxConsts.MINUS + oper);
                }

                //连接符
                conc = Integer.parseInt(arrArg[3]);
                switch (conc)
                {
                    case 1:
                        arrArg[3] = " AND ";
                        break;
                    case 2:
                        arrArg[3] = " OR ";
                        break;
                    default:
                        arrArg[3] = "";
                        break;
                }

                //拼 列 + 操作符 + 值
                switch (oper)
                {
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                        sqlt2 = arrArg[0] + arrArg[1] + "#{values." + paraNum + "}";                //col = #{values.n
                        CxParam.paramsObj(values, "" + paraNum, typeS, arrArg[2].compareToIgnoreCase(CxConsts.$null) == 0 ? null : arrArg[2]);
                        paraNum++;
                        break;
                    case 8:
                    case 9:
                        sqlt2 = arrArg[0] + arrArg[1];                                                //col IS NULL
                        break;
                    case 10:
                        sqlt2 = arrArg[0] + arrArg[1] + "(" + SqlUtil.paramIn(arrArg[2]) + ")";        //col IN('***','***')
                        break;
                }

                //拼连接符
                if (i == 0 && exp.isEmpty()) sqlt1 += sqlt2;
                else
                {
                    if (conc != 1 && conc != 2) throw new Exception(CxErrors.errParam + CxConsts.MINUS + conc);
                    sqlt1 += arrArg[3] + sqlt2;
                }
            }

            exp += sqlt1;
        }

        //执行SQL
        String sql = "SELECT " + sqlCols + " FROM " + sqlTables + (exp != null && !exp.isEmpty() ? " WHERE " + exp : "");

        rs = mapBase.stat(sql, values);

        CxRet.setSuccess(jo, rs, rs.size() == 1);
        return jo;
    }

    /**
     * 添加一条数据(无ID)
     *
     * @param params 值
     * @return 影响行数
     * @throws Exception 异常
     */
    public ObjectNode insertNoId(JsonNode params) throws Exception
    {
        CxEntity            entity = getEntity(params);
        List<CxFld>         flds   = getFldList(entity.getName());
        String              table  = entity.getName();
        Map<String, Object> values = new HashMap<>();
        ObjectNode          jo     = CxJson.objectNode();
        int                 ret;

        String dbId = getDbId();
        int    srid = getSrid(params);
        
        CxParam.getGeomSqlParams(params, dbId, srid);

        CxParam.paramsObj(values, flds, params);
        values.put(CxKeys.id, "");
        ret = mapBase.insertNoId(table, values, srid);

        if (ret == 1)
        {
            String                    retAtt;
            List<Map<String, Object>> rs = new ArrayList<>(1);
            Map<String, Object>       data;

            //如果需要返回数据
            retAtt = CxParam.getStr(params, CxKeys.retAtt);
            if (retAtt != null && retAtt.equals(CxConsts.V1))
            {
                CxUnionTables tables = new CxUnionTables(entity, cxEntity, cxFld);
                String        cols   = tables.getColnames(flds, dbId, true);
                data = mapBase.queryByParams(table, cols, values, null).get(0);
            }
            else
            {
                data = mapBase.queryByParams(table, CxKeys.id, values, null).get(0);
            }
            rs.add(data);
            CxRet.setSuccess(jo, rs, true);
        }
        else CxRet.setSuccess(jo);
        return jo;
    }

    /**
     * 添加一条数据(有ID)
     *
     * @param entity 实体对象
     * @param values 值
     * @return 是否添加成功
     * @throws Exception 异常
     */
    public Object insertWithId(CxEntity entity, Map<String, Object> values) throws Exception
    {
        int ret;
        int srid = 4326; // 默认SRID

        ret = mapBase.insertNoId(entity.getName(), values, srid);
//        cxSvrID.proId(CxParam.getTable(ctt), values);

//        return ret == 1 ? CxParam.getId(values) : 0;
        return 0;
    }

    /**
     * 添加多条数据(无ID)
     *
     * @param entity 实体对象
     * @param values 值
     * @return 影响行数
     * @throws Exception 异常
     */
    public int insertsNoId(CxEntity entity, List<Map<String, Object>> values) throws Exception
    {
        String table = entity.getName();
        int    i, num, ret;
        int    srid = 4326; // 默认SRID

        num = values.size();

        //增加ID
        for (i = 0; i < num; i++)
        {
            values.get(i).put(CxKeys.id, "");
        }

        ret = mapBase.insertBatchNoId(table, values, srid);
//        cxSvrID.proIds(table, values);

        return 0;
    }

    /**
     * 添加一条数据(有ID)
     *
     * @param params 参数
     * @return 添加结果
     * @throws Exception 异常
     */
    public ObjectNode insertWithId(JsonNode params) throws Exception
    {
        CxEntity            entity = getEntity(params);
        List<CxFld>         flds   = getFldList(entity.getName());
        String              table  = entity.getName();
        int                 id     = CxParam.getId(params);
        Map<String, Object> values = new HashMap<>();
        ObjectNode          jo     = CxJson.objectNode();
        int                 ret;

        String dbId = getDbId();
        int    srid = getSrid(params);
        
        CxParam.getGeomSqlParams(params, dbId, srid);

        CxParam.paramsObj(values, flds, params);
        values.put(CxKeys.id, id);
        ret = mapBase.insertWithId(table, values, srid);

        CxRet.setSuccess(jo);
        if (ret == 1)
        {
            String                    retAtt;
            List<Map<String, Object>> rs = new ArrayList<>(1);
            Map<String, Object>       data;

            //如果需要返回数据
            retAtt = CxParam.getStr(params, CxKeys.retAtt);
            if (retAtt != null && retAtt.equals(CxConsts.V1))
            {
                CxUnionTables tables = new CxUnionTables(entity, cxEntity, cxFld);
                String        cols   = tables.getColnames(flds, dbId, true);
                data = mapBase.queryByParams(table, cols, values, null).get(0);
            }
            else
            {
                data = mapBase.queryByParams(table, CxKeys.id, values, null).get(0);
            }
            rs.add(data);
            CxRet.setSuccess(jo, rs, true);
        }
        else CxRet.setSuccess(jo);
        return jo;
    }

    /**
     * 添加多条数据(无ID)
     *
     * @param params 参数
     * @return 添加结果
     * @throws Exception 异常
     */
    public ObjectNode insertsNoId(JsonNode params) throws Exception
    {
        CxEntity                  entity = getEntity(params);
        List<CxFld>               flds   = getFldList(entity.getName());
        String                    table  = entity.getName();
        List<Map<String, Object>> values = new ArrayList<>();
        ObjectNode                jo     = CxJson.objectNode();
        List<Map<String, Object>> rs;
        Map<String, Object>       data;
        int                       id;
        int                       i, num;

        int srid = getSrid(params);
        
        CxParam.paramsObj(values, flds, null, params);
        num = values.size();

        //增加ID
        for (i = 0; i < num; i++)
            values.get(i).put(CxKeys.id, "");

        num = mapBase.insertBatchNoId(table, values, srid);

        rs = new ArrayList<>(num);

        for (i = 0; i < num; i++)
        {
            data = new HashMap<>(1);
            id = CxParam.getId(values.get(i));
            data.put(CxKeys.id, id);
            rs.add(data);
        }

        CxRet.setSuccess(jo, rs, num == 1);
        return jo;
    }

    /**
     * 删除一条数据（根据ID）
     *
     * @param table 表名
     * @param id    ID
     * @return 影响行数
     * @throws Exception 异常
     */
    public Object del(String table, int id) throws Exception
    {
        return mapBase.deleteById(table, id);
    }

    /**
     * 删除一条数据（根据ID）
     *
     * @param entity 实体对象
     * @param id     ID
     * @return 影响行数
     * @throws Exception 异常
     */
    public int del(CxEntity entity, int id) throws Exception
    {
        return mapBase.deleteById(entity.getName(), id);
    }

    /**
     * 删除一条数据（根据ID）
     *
     * @param params 参数
     * @return 删除结果
     * @throws Exception 异常
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = {Exception.class})
    public ObjectNode del(JsonNode params) throws Exception
    {
        CxEntity                  entity = getEntity(params);
        List<CxFld>               flds   = getFldList(entity.getName());
        String                    table  = entity.getName();
        ObjectNode                jo    = CxJson.objectNode();
        List<Map<String, Object>> rs    = new ArrayList<>(1);

        ;
        Map<String, Object> data;
        String              retAtt;

        //如果需要返回数据
        String dbId = getDbId();
        retAtt = CxParam.getStr(params, CxKeys.retAtt);
        int id = CxParam.getId(params);
        if (retAtt != null && retAtt.equals(CxConsts.V1))
        {
            CxUnionTables tables = new CxUnionTables(entity, cxEntity, cxFld);
            String        cols   = tables.getColnames(flds, dbId, true);
            data = mapBase.queryById(table, cols, id);
        }
        else
        {
            data = new HashMap<>(1);
            data.put(CxKeys.id, id);
        }
        mapBase.deleteById(table, id);
        rs.add(data);
        CxRet.setSuccess(jo, rs, true);

        return jo;
    }

    /**
     * 删除多条数据（根据ID）
     *
     * @param params 参数
     * @return 删除结果
     * @throws Exception 异常
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = {Exception.class})
    public ObjectNode dels(JsonNode params) throws Exception
    {
        CxEntity                  entity = getEntity(params);
        String                    table  = entity.getName();
        String                    ids   = CxParam.getStr(params, CxKeys.id);
        ObjectNode                jo    = CxJson.objectNode();
        List<Map<String, Object>> rs;
        Map<String, Object>       data;

        String[] arrId  = ids.split(CxConsts.defSep);
        int      id;
        int      i, num = arrId.length;

        rs = new ArrayList<>(num);
        for (i = 0; i < num; i++)
        {
            id = Integer.parseInt(arrId[i]);
            mapBase.deleteById(table, id);

            data = new HashMap<>(1);
            data.put(CxKeys.id, id);
            rs.add(data);
        }

        CxRet.setSuccess(jo, rs, num == 1);
        return jo;
    }

    /**
     * 修改一条数据（根据ID）
     *
     * @param params 参数
     * @return 更新结果
     * @throws Exception 异常
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = {Exception.class})
    public ObjectNode update(JsonNode params) throws Exception
    {
        CxEntity            entity = getEntity(params);
        List<CxFld>         flds   = getFldList(entity.getName());
        String              table  = entity.getName();
        String              cols   = CxParam.getStr(params, CxKeys.cols);
        Map<String, Object> values = new HashMap<>();
        ObjectNode          jo     = CxJson.objectNode();
        int                 id     = CxParam.getId(params);
        int                 ret;

        String dbId = getDbId();
        int    srid = getSrid(params);
        
        CxParam.getGeomSqlParams(params, dbId, srid);

        CxParam.paramsObj(values, flds, cols, params);
        ret = mapBase.updateById(table, values, id, srid);

        if (ret == 1)
        {
            String                    retAtt;
            List<Map<String, Object>> rs = new ArrayList<>(1);
            Map<String, Object>       data;
            //如果需要返回数据
            retAtt = CxParam.getStr(params, CxKeys.retAtt);
            if (retAtt != null && retAtt.equals(CxConsts.V1))
            {
                CxUnionTables tables = new CxUnionTables(entity, cxEntity, cxFld);
                String        strcol = tables.getColnames(flds, dbId, true);
                data = mapBase.queryById(table, strcol, id);
            }
            else
            {
                data = new HashMap<>(1);
                data.put(CxKeys.id, id);
            }
            rs.add(data);
            CxRet.setSuccess(jo, rs, true);
        }
        else CxRet.setSuccess(jo);
        return jo;
    }

    /**
     * 修改多条数据（根据ID）
     *
     * @param params 参数
     * @return 更新结果
     * @throws Exception 异常
     */
    public ObjectNode updates(JsonNode params) throws Exception
    {
        CxEntity                  entity = getEntity(params);
        List<CxFld>               flds   = getFldList(entity.getName());
        String                    table  = entity.getName();
        String                    cols   = CxParam.getStr(params, CxKeys.cols);
        String                    ids    = CxParam.getStr(params, CxKeys.id);
        List<Map<String, Object>> values = new ArrayList<>();
        ObjectNode                jo     = CxJson.objectNode();
        List<Map<String, Object>> rs;
        Map<String, Object>       data;

        String[] arrId = ids.split(CxConsts.defSep);
        int      id;
        int      i, num;

        String dbId = getDbId();
        int    srid = getSrid(params);
        
        CxParam.getGeomSqlArrayParams(params, dbId, srid);

        CxParam.paramsObj(values, flds, cols, params);
        num = values.size();
        rs = new ArrayList<>(num);

        for (i = 0; i < num; i++)
        {
            id = Integer.parseInt(arrId[i]);
            mapBase.updateById(table, values.get(i), id, srid);

            data = new HashMap<>(1);
            data.put(CxKeys.id, id);
            rs.add(data);
        }

        CxRet.setSuccess(jo, rs, num == 1);
        return jo;
    }


    /**
     * 调用存储过程
     *
     * @param params 参数
     * @return 调用结果
     * @throws Exception 异常
     */
    public ObjectNode callProc(JsonNode params) throws Exception
    {
        CxEntity entity = getEntity(params);
        CxSqlPro sqlExp;
        String   pro = null;
        String   sep = null;

        String   strTypesC = null;                                        //配置 参数类型-字符串
        String   strValsC  = null;                                        //配置 参数值-字符串
        String[] arrTypeC  = null;                                        //配置 参数类型-数组
        String[] arrValC   = null;                                            //配置 参数值-数组
        int      paraNumC;                                                //配置 参数-个数

        String[] arrValP = null;                                            //传参 参数值-数组
        int      paraNum;                                                //当前参数个数

        String key      = CxParam.getStr(params, CxKeys._key);
        String strValsP = CxParam.getStr(params, CxKeys.vals);         //服务调用传的 vals

        String str;
        int    i, j, num;

        //取配置对象
        if (key != null && !key.isEmpty())
        {
            // 2、取usersql值
            sqlExp = cxSqlExp.queryCxSqlPro(key);
            if (CxValueUtil.isEmpty(sqlExp))
            {
                throw new Exception("在cx_sqlexp中未找key字段");
            }

            if (sqlExp == null) throw new Exception(CxErrors.errKey + CxConsts.MINUS + key);
            pro = sqlExp.getPro();
            strTypesC = sqlExp.getTypes();
            strValsC = sqlExp.getVals();
            sep = sqlExp.getSep();
        }

        //分隔符
        if (sep == null || sep.isEmpty()) sep = CxConsts.defSep;

        //传参 参数值
        if (strValsP != null && !strValsP.isEmpty())
        {
            arrValP = strValsP.split(sep);
            paraNum = arrValP.length;
            //检查值
            for (i = 0; i < paraNum; i++)
                if (arrValP[i].compareToIgnoreCase(CxConsts.$null) == 0) arrValP[i] = null;
        }

        //配置 参数类型和值
        if (strTypesC != null && !strTypesC.isEmpty())
        {
            char ch;

            arrTypeC = strTypesC.split(CxConsts.defSep);
            paraNumC = arrTypeC.length;
            arrValC = strValsC.split(sep);

            if (paraNumC != arrValC.length)
                throw new Exception(CxErrors.errValType + CxConsts.MINUS + strTypesC + " " + strValsC);

            //用传来的值去替换配置中值，配置里面写法如：{1}，{2} 分别对应传递进来的值, {var} 这样的格式替换传进来的变量
            for (i = 0; i < paraNumC; i++)
            {
                str = arrValC[i];
                num = str.length();
                if (num >= 3 && str.charAt(0) == '{' && str.charAt(num - 1) == '}')
                {
                    str = str.substring(1, num - 1);
                    ch = str.charAt(0);
                    if (ch >= '0' && ch <= '9')
                    {
                        j = Integer.parseInt(str) - 1;
                        if (j >= arrValP.length || j < 0) throw new Exception(CxErrors.errParam + CxConsts.MINUS + str);
                        arrValC[i] = arrValP[j];
                    }
                    else arrValC[i] = CxParam.getStr(params, str);
                }
            }
        }

        paraNum = arrValC != null ? arrValC.length : 0;

        //调用存储过程
        ArrayList<CxPro>          values = new ArrayList<CxPro>();
        ObjectNode                jo     = CxJson.objectNode();
        List<Map<String, Object>> rs;

        CxParam.paramsObj(values, paraNum, arrTypeC, arrValC);
        rs = mapBase.callProcedure(pro, convertToMapList(values));

        CxRet.setSuccess(jo, rs, rs.size() == 1);
        return jo;
    }

    /**
     * 调用存储过程
     *
     * @param pro    过程名称
     * @param params 参数
     * @return 调用结果
     * @throws Exception 异常
     */
    public Object callProc(String pro, List<CxPro> params) throws Exception
    {
        return mapBase.callProcedure(pro, convertToMapList(params));
    }

    /**
     * 批量操作
     *
     * @param params 参数
     * @return 更新结果
     * @throws Exception 异常
     */
    public ObjectNode batch(JsonNode params) throws Exception
    {
        return new CxSvrBatch().run(params, dstm, this);
    }

    /**
     * 插入数据操作日志
     *
     * @param entity 实体对象
     * @param params 参数
     * @param id     ID
     * @param type   操作类型
     * @param data   操作数据
     * @return 插入数据的数量
     * @throws Exception 异常
     */
    public int insOperLog(CxEntity entity, JsonNode params, int id, int type, Map<String, Object> data) throws Exception
    {
        Map<String, Object> values = new HashMap<>(6);

        values.put(CxKeys.major, entity.getMajor());
        values.put(CxKeys.minor, entity.getMinor());
        values.put(CxKeys.eid, id);
        values.put(CxKeys.userid, CxParam.getInt(params, CxKeys._userId, 0));
        values.put(CxKeys.username, CxParam.getStr(params, CxKeys._userName));
        values.put(CxKeys.type, type);
        values.put(CxKeys.att, data.toString().replace(CxConsts.$null, ""));

        return mapBase.insertOperLog("", values);
    }

    public ObjectNode init(JsonNode params) throws Exception
    {
        ObjectNode jo      = CxJson.objectNode();
        ObjectNode alldata = CxJson.objectNode();
        int        sys     = params.get("sys").asInt();

        ObjectNode data = (ObjectNode) CxJson.om.readTree(alldata.toString());

        // 遍历所有表缓存配置，从Redis获取格式化的缓存数据
        Map<String, String> tableCacheConfig = TableCacheService.getTableCacheConfig();
        for (Map.Entry<String, String> entry : tableCacheConfig.entrySet())
        {
            String cacheKey = entry.getKey();
            String tableName = entry.getValue();
            
            Map<String, Object> cachedData = tableCacheService.getCachedTableDataAsJson(cacheKey);
            if (cachedData != null)
            {
                data.putPOJO(tableName, cachedData);
            }
        }

        data.put("time", CxDate.getNowYMDHMSTime());
        CxRet.setSuccess(jo, data, true);
        return jo;
    }

    public static String replaceChar(String exp, String dest)
    {
        int pos = exp.indexOf('?');

        return pos >= 0 ? exp.substring(0, pos) + dest + exp.substring(pos + 1) : exp;
    }

    /**
     * 从参数中获取实体对象
     */
    private CxEntity getEntity(JsonNode params) throws Exception
    {
        String major = CxParam.getStr(params, CxKeys._major);
        String minor = CxParam.getStr(params, CxKeys._minor);
        return cxEntity.queryEntity(major, minor);
    }

    /**
     * 获取数据库类型标识
     * @return 数据库类型字符串: "oracle", "mysql", "postgresql", "sqlserver", "dm8"
     */
    private String getDbId()
    {
        DbType dbType = JdbcUtils.getDbType();
        switch (dbType)
        {
            case ORACLE:
            case ORACLE_12C:
                return "oracle";
            case MYSQL:
            case MARIADB:
                return "mysql";
            case POSTGRE_SQL:
                return "postgresql";
            case SQL_SERVER:
            case SQL_SERVER2005:
                return "sqlserver";
            case DM:
                return "dm8";
            default:
                return "mysql"; // 默认返回mysql
        }
    }

    /**
     * 获取空间参考系统标识符(SRID)
     * 优先从参数中获取，如果没有则返回默认值
     * @param params 参数
     * @return SRID值
     */
    private int getSrid(JsonNode params) throws Exception
    {
        // 优先从参数中获取srid
        int srid = CxParam.getInt(params, CxKeys.srid, 0);
        if (srid > 0)
        {
            return srid;
        }
        // 返回默认值，常用的SRID：4326(WGS84), 3857(Web Mercator), 4490(CGCS2000)
        return 4326;
    }

    /**
     * 获取字段列表
     */
    private List<CxFld> getFldList(String tableName)
    {
        return cxFld.queryFldList(tableName);
    }

    /**
     * 转换CxPro列表为Map列表
     */
    private List<Map<String, Object>> convertToMapList(List<CxPro> proList)
    {
        List<Map<String, Object>> result = new ArrayList<>();
        for (CxPro pro : proList)
        {
            Map<String, Object> map = new HashMap<>();
            map.put("jdbcType", pro.jdbcType);
            map.put("value", pro.value);
            result.add(map);
        }
        return result;
    }
}
